﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using Microsoft.FSharp.Core;

namespace CoreReset {

    using ContextGraphDict = Dictionary<ContextGraph.Context, Dictionary<ContextGraph.Context, FSharpRef<setOfPairs.DFR>>>;

    /// <summary>
    /// This delgate is used in order to invoke the ClearLastHighlightedCommand method
    /// from a different thread (the analysis thread).
    /// </summary>
    internal delegate void ClearLastHighlightedCommandCallback();

    /// <summary>
    /// This delgate is used in order to invoke the setAnalyseButtonImage method
    /// from a different thread (the analysis thread).
    /// </summary>
    /// <param name="cancel">If true, the Analyse button is changed to a "cancel" button.</param>
    internal delegate void SetAnalyseButtonImageCallback(bool cancel);

    /// <summary>
    /// This delgate is used in order to invoke the setResultText method
    /// from a different thread (the analysis thread).
    /// </summary>
    /// <param name="text">The result text.</param>
    /// <param name="g">The computed context graph.</param>
    internal delegate void SetResultTextCallback(string text, ContextGraphDict g);
    /// <summary>
    /// This delgate is used in order to invoke the UpdateCommandGraph method
    /// from a different thread (the analysis thread).
    /// </summary>
    /// <param name="args">Command Graph arguments - containing a command and its associated context graph.</param>
    internal delegate void UpdateProgramTreeCallback(FSEvent.CommandGraphUpdatedEventArgs args);

    /// <summary>
    /// This is the main application form.
    /// </summary>
    public sealed partial class MainForm : Form {

        /// <summary>
        /// Placeholder for the "Analyse" button tool-tip.
        /// </summary>
        private readonly ToolTip m_analyseToolTip;

        /// <summary>
        /// Window title excluding current file name and dirty flag.
        /// </summary>
        private readonly string m_baseTitle;

        /// <summary>
        /// Placeholder for other button tool-tips.
        /// </summary>
        private readonly ToolTip m_generalToolTip;

        /// <summary>
        /// The current abstract interpreter algorithm object.
        /// </summary>
        private readonly ABSTRACT_INTERPRETER_ALGORITHM.AbstractInterpreter m_absInt = ABSTRACT_INTERPRETER_ALGORITHM.absInt;

        /// <summary>
        /// Placeholder for the analysis thread.
        /// </summary>
        private volatile Thread m_analysisThread;

        /// <summary>
        /// Last saved code text, for dirty flag computation.
        /// </summary>
        private string m_baseCodeValue;

        /// <summary>
        /// Reference to the Command Graph form.
        /// </summary>
        private CommandGraphForm m_commandGraphForm;

        /// <summary>
        /// Current file name loaded, or null if none.
        /// </summary>
        private string m_currentFileName;

        /// <summary>
        /// Reference to the debug log form.
        /// </summary>
        private volatile DebugLogForm m_debugLogForm;

        /// <summary>
        /// This flag is set to true during closing of this form.
        /// </summary>
        private bool m_formClosing;

        /// <summary>
        /// Reference to the last computed AST.
        /// </summary>
        private CoreEx.Cmd m_lastAST;

        /// <summary>
        /// The AST tree node which was last highlighted.
        /// </summary>
        private TreeNode m_lastHighlightedNode;

        /// <summary>
        /// When true, the analysis thread dumps debug information using a
        /// specialized event.
        /// </summary>
        private volatile bool m_registeredToDebugEvent;

        /// <summary>
        /// The analysis thread continues as long as this flag is true.
        /// </summary>
        private volatile bool m_shouldContinue;

        /// <summary>
        /// When true, analysis will be executed step-by-step.
        /// </summary>
        private volatile bool m_stepByStep;
        /// <summary>
        /// Constructor.
        /// </summary>
        public MainForm() {
            InitializeComponent();

            m_analyseToolTip = new ToolTip();
            m_analyseToolTip.SetToolTip(buttonAnalyse, "Analyse program");
            m_generalToolTip = new ToolTip();
            m_generalToolTip.SetToolTip(buttonOpenCodeFile, "Open Core Language program file");
            m_generalToolTip.SetToolTip(buttonContinue, "Analyse program step-by-step");
            m_generalToolTip.SetToolTip(buttonShowDebugLog, "Show analysis log");

            textBoxAnalysisResult.Font = new Font(textBoxAnalysisResult.Font.FontFamily,
                                            (float)Properties.Settings.Default.resultFontSize);

            CodeTextBox.Font = new Font(CodeTextBox.Font.FontFamily,
                                            (float)Properties.Settings.Default.codeFontSize);

            MRU.UpdateMRUMenu(recentFilesToolStripMenuItem);
            MRU.ItemClicked += MRU_ItemClicked;
            m_baseTitle = Text;
            m_baseCodeValue = CodeTextBox.Text;
            UpdateTitle();
        }

        protected override bool ProcessCmdKey(ref Message msg, Keys keyData) {
            if (keyData == (Keys.F5)) {
                buttonAnalyse.PerformClick();
            }

            return base.ProcessCmdKey(ref msg, keyData);
        }

        /// <summary>
        /// Given a command identifier, returns the number of commands on its premises
        /// (0, 1 or 2).
        /// </summary>
        /// <param name="id">The command identifier.</param>
        /// <returns>The number of commands on the command premises.</returns>
        public int ChildNumberUnderBranch(int id) {
            int rc = 0;
            var node = GetTreeNodeById(ASTreeView.TopNode, id) as CmdTreeNode;
            if (node != null) {
                var parent = node.Parent as CmdTreeNode;
                if (parent != null && parent.Text.StartsWith("Choose")) {
                    rc = parent.Nodes[0] == node ? 1 : 2;
                }
            }

            return rc;
        }

        /// <summary>
        /// Retrieves the text displayed in the AST for a command by its identifier.
        /// </summary>
        /// <param name="id">The command identifier.</param>
        /// <returns>the text displayed in the AST for the command.</returns>
        public string GetCommandText(int id) {
            string txt = "";
            var node = GetTreeNodeById(ASTreeView.TopNode, id) as CmdTreeNode;
            if (node != null)
                txt = node.Text;
            return txt;
        }

        internal void OnCommandGraphFormClosing() {
            m_commandGraphForm = null;
        }

        internal void OnDebugLogFormClosing() {
            if (m_absInt != null) {
                ABSTRACT_INTERPRETER_ALGORITHM.absInt.setDebugMode(false);
                if (m_registeredToDebugEvent) {
                    m_absInt.DebugInfoAdded -= OnDebugInfoAdded;
                    m_registeredToDebugEvent = false;
                }
            }
            buttonShowDebugLog.Enabled = true;
        }

        /// <summary>
        /// Extracts a CmdEx command from a CoreEx.Cmd.
        /// This can either be a Naive analysis algorithm command or
        /// an abstract interpreter algorithim command (associated with
        /// a context graph).
        /// </summary>
        private static CoreEx.CmdEx GetCmdEx(CoreEx.Cmd cmd) {
            return cmd.Item1;
        }

        private void AbortAnalysis() {
            if (m_analysisThread != null) {
                m_analysisThread.Abort();
                m_analysisThread = null;
                if (!m_formClosing) {
                    setAnalyseButtonImageSafe(false);
                    UnregisterAnalysisEvents();
                    textBoxAnalysisResult.Clear();
                    textBoxAnalysisResult.Text = "Analysis aborted.";

                    m_stepByStep = false;
                }
            }
        }

        /// <summary>
        /// Adds an AST node for the given command.
        /// </summary>
        /// <param name="parent">The new AST node is added as a child of 'parent'.</param>
        /// <param name="cmd">The command for which the new AST node is created.</param>
        private void addASTNode(CmdTreeNode parent, CoreEx.Cmd cmd) {
            CmdTreeNode child;
            CoreEx.CmdEx c = GetCmdEx(cmd);
            if (c.IsChoice) {
                child = new CmdTreeNode(cmdId(cmd), "Choose / or (" + cmdId(cmd) + ")");
                var choice = c as CoreEx.CmdEx.Choice;
                addASTNode(child, choice.Item1);
                addASTNode(child, choice.Item2);
            } else if (c.IsLoop) {
                var loop = c as CoreEx.CmdEx.Loop;
                child = new CmdTreeNode(cmdId(cmd), "Loop " + getVarNameByVarIndex(loop.Item1) + " (" + cmdId(cmd) + ")");
                addASTNode(child, loop.Item2);
            } else if (c.IsSeq) {
                child = new CmdTreeNode(cmdId(cmd), "Sequence" + " (" + cmdId(cmd) + ")");
                var seq = c as CoreEx.CmdEx.Seq;
                addASTNode(child, seq.Item1);
                addASTNode(child, seq.Item2);
            } else if (c.IsSkip) {
                child = new CmdTreeNode(cmdId(cmd), "Skip" + " (" + cmdId(cmd) + ")");
            } else if (c.IsAsgn) {
                var asgn = c as CoreEx.CmdEx.Asgn;
                string expr = exprString(asgn.Item2);
                int varIndex = asgn.Item1;
                string varName = getVarNameByVarIndex(varIndex);
                child = new CmdTreeNode(cmdId(cmd), varName + " := " + expr + " (" + cmdId(cmd) + ")");
            } else if (c.IsGoto) {
                var gotocmd = c as CoreEx.CmdEx.Goto;
                child = new CmdTreeNode(cmdId(cmd), "Goto " + gotocmd.Item);
            } else if (c.IsLabel) {
                var labelcmd = c as CoreEx.CmdEx.Label;
                child = new CmdTreeNode(cmdId(cmd), "Label " + labelcmd.Item);   
            } else {
                throw new Exception("command not supported!");
            }

            parent.Nodes.Add(child);
        }

        private void ai_CommandGraphUpdated(object sender, EventArgs e) {
            var cmdArgs = e as FSEvent.CommandGraphUpdatedEventArgs;
            if (cmdArgs != null)
                safeUpdateCommandGraph(cmdArgs);
            while (!m_shouldContinue) {
                Thread.Sleep(10);
            }

            m_shouldContinue = false;
        }

        private void Analyze(bool inSteps) {
            var code = CodeTextBox.Text;
            m_shouldContinue = false;
            m_stepByStep = inSteps;

            try {
                var parsedCode = Parser.parseString(code);
                var c = AstProcessor.astBreakNestedExpressions(parsedCode);
                AstProcessor.checkGotoCommandsValidity(c);
                m_lastAST = CoreEx.extendedAbsIntAST(c);

                updateAST(m_lastAST);
                if (m_debugLogForm != null)
                    m_debugLogForm.ClearText();

                setAnalyseButtonImageSafe(true);
                m_analysisThread = new Thread(ThreadedAnalysisFunc);
                textBoxAnalysisResult.Clear();
                textBoxAnalysisResult.Text = "Analysing...";
                m_analysisThread.Start(new AnalysisThreadParams(c));
            } catch (Exception f) {
                var message = String.Format("Error: {0}\n\n Stack Trace:\n, {1}", f.Message, f.StackTrace);
                MessageBox.Show(message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                m_analysisThread = null;
                setAnalyseButtonImageSafe(false);
            }
        }

        private void analyzeToolStripMenuItem_Click(object sender, EventArgs e) {
            Analyze(false);
        }

        private void ASTreeView_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e) {
            var cmdNode = e.Node as CmdTreeNode;
            ShowCommandGraph(cmdNode, true);
        }

        private void buttonAbout_Click(object sender, EventArgs e) {
            var frm = new AboutForm();
            frm.ShowDialog();
        }

        private void buttonAnalyse_Click(object sender, EventArgs e) {
            if (m_analysisThread != null) {
                //analysis is running - abort it.
                AbortAnalysis();
            } else {
                Analyze(false);
            }
        }

        private void buttonContinue_Click(object sender, EventArgs e) {
            if (!m_stepByStep) {
                m_stepByStep = true;
                Analyze(true);
            } else {
                m_shouldContinue = true;
            }
        }

        private void buttonShowDebugLog_Click(object sender, EventArgs e) {
            m_absInt.setDebugMode(true);
            if (m_debugLogForm == null) {
                m_debugLogForm = new DebugLogForm(this);
                m_debugLogForm.Font = new Font(m_debugLogForm.Font.FontFamily,
                                            (float)Properties.Settings.Default.analysisFontSize);
            }
            if (!m_debugLogForm.Visible) {
                m_debugLogForm.Show(this);
                buttonShowDebugLog.Enabled = false;
            }
            if (m_absInt != null && !m_registeredToDebugEvent) {
                m_registeredToDebugEvent = true;
                m_absInt.DebugInfoAdded += OnDebugInfoAdded;
            }

            Analyze(false);
        }

        /// <summary>
        /// Check if there are unsaved changes, if so, prompt user to save, discard or cancel the
        /// operation.
        /// </summary>
        /// <returns>Returns true if user elected to cancel the operation, false otherwise.</returns>
        private bool CheckUnsavedChanges() {
            bool cancel = false;
            if (CodeTextBox.Text != m_baseCodeValue) {
                var rc = MessageBox.Show("You have unsaved changes! Save them?", "Warning", MessageBoxButtons.YesNoCancel);
                if (rc == DialogResult.Yes) {
                    if (!doSaveOrSaveAs()) cancel = true;
                } else if (rc == DialogResult.Cancel) {
                    cancel = true;
                }
            }
            return cancel;
        }

        private void ClearLastHighlightedCommand() {
            if (m_lastHighlightedNode != null) {
                m_lastHighlightedNode.BackColor = Color.White;
                m_lastHighlightedNode.ForeColor = Color.Black;
            }
        }

        private void clearLastHighlightedCommandSafe() {
            if (ASTreeView.InvokeRequired) {
                var d = new ClearLastHighlightedCommandCallback(ClearLastHighlightedCommand);
                Invoke(d, new object[] { });
            } else {
                ClearLastHighlightedCommand();
            }
        }

        /// <summary>
        /// Returns a command identifier as integer.
        /// </summary>
        private int cmdId(CoreEx.Cmd cmd) {
            return cmd.Item3;
        }

        private void CodeTextBox_TextChanged(object sender, EventArgs e) {
            UpdateTitle();
        }

        private void CoreResetForm_FormClosing(object sender, FormClosingEventArgs e) {
            // File management - prompt user to save changes before quitting.
            if (CheckUnsavedChanges()) {
                e.Cancel = true;
                return;
            }

            m_formClosing = true;
            AbortAnalysis();
            if (m_commandGraphForm != null) {
                m_commandGraphForm.Close();
            }
            if (m_debugLogForm != null) {
                m_debugLogForm.Close();
            }
        }

        /// <summary>
        /// Save current file, assumes there is a current file.
        /// </summary>
        /// <returns>Returns true if successful, false otherwise.</returns>
        private bool doSave() {
            try {
                File.WriteAllText(m_currentFileName, CodeTextBox.Text);
                MRU.Insert(m_currentFileName);
                MRU.UpdateMRUMenu(recentFilesToolStripMenuItem);
                m_baseCodeValue = CodeTextBox.Text;
                UpdateTitle();
                return true;
            } catch (Exception x) {
                MessageBox.Show("Failed to save (" + x.Message + ")");
                return false;
            }
        }

        /// <summary>
        /// Prompt the user for a target file path and save the current code there.
        /// </summary>
        /// <returns>Returns true if successful, false otherwise.</returns>
        private bool doSaveAs() {
            var ofd = new SaveFileDialog { Filter = "Core language program file (*.core)|*.core", AddExtension = true };
            if (ofd.ShowDialog() == DialogResult.OK) {
                m_currentFileName = ofd.FileName;
                return doSave();
            }
            return false;
        }

        /// <summary>
        /// Calls 'save' or 'save as' depending on what's appropriate.
        /// </summary>
        /// <returns>Return true if the save was successful, false otherwise.</returns>
        private bool doSaveOrSaveAs() {
            return string.IsNullOrEmpty(m_currentFileName) ? doSaveAs() : doSave();
        }

        /// <summary>
        /// Converts a core language expression to string.
        /// </summary>
        private string exprString(Core.Expr expr) {
            string res = null;
            if (expr.IsPlus) {
                var plus = expr as Core.Expr.Plus;
                res = exprString(plus.Item1) + " + " + exprString(plus.Item2);
            } else if (expr.IsTimes) {
                var times = expr as Core.Expr.Times;
                res = exprString(times.Item1) + " * " + exprString(times.Item2);
            } else if (expr.IsVar) {
                var v = expr as Core.Expr.Var;
                res = getVarNameByVarIndex(v.Item);
            } else if (expr.IsZero) {
                res = "0";
            } else if (expr.IsOne) {
                res = "1";
            }

            return res;
        }

        private TreeNode GetTreeNodeById(TreeNode root, int id) {
            var node = root as CmdTreeNode;
            if (node != null) {
                if (node.Id == id)
                    return root;

                var treeNode = root.Nodes.Cast<TreeNode>();
                return treeNode.Select(child => GetTreeNodeById(child, id)).FirstOrDefault(n => n != null);
            }

            return null;
        }

        /// <summary>
        /// Retrieves the variable name by its index.
        /// Will work only after an analysis was executed.
        /// </summary>
        /// <param name="varIndex"></param>
        /// <returns></returns>
        private string getVarNameByVarIndex(int varIndex) {
            return Parser.parserLastVarToIndexMap()[varIndex];
        }

        private void HighlightCommand(CmdTreeNode cmdNode) {
            cmdNode.BackColor = Color.Blue;
            cmdNode.ForeColor = Color.Yellow;
            m_lastHighlightedNode = cmdNode;
        }

        /// <summary>
        /// Event procedure when clicking on an MRU entry from the 'Recent Files' menu.
        /// </summary>
        private void MRU_ItemClicked(object sender, MRUEventArgs e) {
            if (CheckUnsavedChanges()) return;
            try {
                var text = File.ReadAllText(e.FileName);
                CodeTextBox.Text = text;
                MRU.Insert(e.FileName);
                MRU.UpdateMRUMenu(recentFilesToolStripMenuItem);
                m_currentFileName = e.FileName;
                m_baseCodeValue = CodeTextBox.Text;
                UpdateTitle();
            } catch (Exception x) {
                MessageBox.Show(x.Message, "Can't open file");
                MRU.Items.Remove(e.FileName);
            }
        }

        private void newToolStripMenuItem_Click(object sender, EventArgs e) {
            if (CheckUnsavedChanges()) return;
            m_currentFileName = null;
            CodeTextBox.Text = "";
            m_baseCodeValue = CodeTextBox.Text;
            UpdateTitle();
        }

        private void OnDebugInfoAdded(object sender, EventArgs args) {
            var eArgs = args as FSEvent.DebugInfoAddedEventArgs;
            if (eArgs != null && m_debugLogForm != null) {
                m_debugLogForm.AddText(eArgs.Text);
            }
        }

        private void openToolStripMenuItem_Click(object sender, EventArgs e) {
            if (CheckUnsavedChanges()) return;
            var ofd = new OpenFileDialog { Filter = "Core language program file (*.core)|*.core" };
            if (ofd.ShowDialog() == DialogResult.OK) {
                //Add the file to the MRU.Items collection using
                //the MRU.Insert method...

                MRU.Insert(ofd.FileName);
                MRU.UpdateMRUMenu(recentFilesToolStripMenuItem);

                using (var tr = new StreamReader(ofd.OpenFile())) {
                    CodeTextBox.Text = tr.ReadToEnd();
                    m_baseCodeValue = CodeTextBox.Text;
                    m_currentFileName = ofd.FileName;
                    UpdateTitle();
                }
            }
        }

        private void RegisterAnalysisEvents() {
            if (m_stepByStep) {
                m_absInt.CommandGraphUpdated += ai_CommandGraphUpdated;
            }
            if (m_registeredToDebugEvent) return;
            m_registeredToDebugEvent = true;
            m_absInt.DebugInfoAdded += OnDebugInfoAdded;
        }

        private void safeUpdateCommandGraph(FSEvent.CommandGraphUpdatedEventArgs args) {
            if (ASTreeView.InvokeRequired) {
                var d = new UpdateProgramTreeCallback(UpdateCommandGraph);
                Invoke(d, new object[] { args });
            } else {
                UpdateCommandGraph(args);
            }
        }

        private void saveAsToolStripMenuItem_Click(object sender, EventArgs e) {
            doSaveAs();
        }

        private void saveToolStripMenuItem_Click(object sender, EventArgs e) {
            doSaveOrSaveAs();
        }

        private void setAnalyseButtonImage(bool cancel) {
            m_analyseToolTip.RemoveAll();
            if (cancel) {
                m_analyseToolTip.SetToolTip(buttonAnalyse, "Analysis In Progress...\nClick to abort.");
                buttonAnalyse.Image = Properties.Resources.Cancel_icon;
            } else {
                m_analyseToolTip.SetToolTip(buttonAnalyse, "Analyse Program");
                buttonAnalyse.Image = Properties.Resources.Icon_Gear02_Blue;
            }
        }

        private void setAnalyseButtonImageSafe(bool cancel) {
            if (buttonAnalyse.InvokeRequired) {
                var d = new SetAnalyseButtonImageCallback(setAnalyseButtonImage);
                Invoke(d, new object[] { cancel });
            } else {
                setAnalyseButtonImage(cancel);
            }
        }

        /// <summary>
        /// Updates the analysis final results to the Analysis Results pane and
        /// updated the Command Graph Form with the last computed graph.
        /// </summary>
        /// <param name="s">Context graph in textual format.</param>
        /// <param name="g">Context graph.</param>
        private void setResultText(string s, ContextGraphDict g) {
            textBoxAnalysisResult.Lines = s.Split('\n');
            if (!m_stepByStep && ASTreeView.Nodes.Count > 0) {
                var node = ASTreeView.Nodes[0].Nodes[0] as CmdTreeNode;
                if (node != null && g != null) {
                    node.GraphText = s;
                    node.Graph = ContextGraph.clone(g);
                    ASTreeView.Select();
                    ASTreeView.SelectedNode = node;
                    //ShowCommandGraph(node, true);
                }
            }
        }
        private void setResultTextSafe(string text, ContextGraphDict g) {
            if (textBoxAnalysisResult.InvokeRequired) {
                var d = new SetResultTextCallback(setResultText);
                Invoke(d, new object[] { text, g });
            } else {
                setResultText(text, g);
            }
        }

        private void ShowCommandGraph(CmdTreeNode cmdNode, bool forceOpen) {
            if (cmdNode != null && cmdNode.GraphText != null) {
                if (m_commandGraphForm == null) {
                    m_commandGraphForm = new CommandGraphForm(this, cmdNode.GraphText, cmdNode.Graph);
                } else {
                    m_commandGraphForm.SetText(cmdNode.GraphText);
                    m_commandGraphForm.SetGraph(cmdNode.Graph);
                }
                if (!m_commandGraphForm.Visible && forceOpen) {
                    m_commandGraphForm.Show(this);
                }
            } else if (m_commandGraphForm != null) {
                m_commandGraphForm.SetText("");
            } else {
                m_commandGraphForm = new CommandGraphForm(this, "", null);
                m_commandGraphForm.Show(this);
            }
        }

        private void ThreadedAnalysisFunc(object configObj) {
            try {
                var config = (AnalysisThreadParams)configObj;
                var ast = config.cmd;
                var startTime = 0;
                if (!m_stepByStep)
                    startTime = Environment.TickCount;

                RegisterAnalysisEvents();
                ContextGraphDict contextGraph = m_absInt.analyse(ast);
                string edgeCount = string.Format("\nContext graph contains {0} edges.", ContextGraph.untaggedEdges(contextGraph).Length.ToString());
                UnregisterAnalysisEvents();
                string resText = string.Format("Context Graph:\n{0}{1}",
                    ContextGraph.prtEdges(contextGraph), m_absInt.resultText(ast, contextGraph));

                m_stepByStep = false;
                var elapsedTime = Environment.TickCount - startTime;
                double dElapsedTime = elapsedTime / 1000.0;
                if (startTime != 0)
                    resText = string.Format("Analysis complete in {0} seconds.{1}\n\n{2}", dElapsedTime.ToString(), edgeCount, resText);

                setResultTextSafe(resText, contextGraph);
            } catch (Exception f) {
                MessageBox.Show(f.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                setResultTextSafe("Analysis failed.", null);
            } finally {m_analysisThread = null;
                if (!m_formClosing) {
                    setAnalyseButtonImageSafe(false);
                    clearLastHighlightedCommandSafe();
                }
            }
        }

        private void UnregisterAnalysisEvents() {
            if (m_stepByStep) {
                m_absInt.CommandGraphUpdated -= ai_CommandGraphUpdated;
            }
        }

        /// <summary>
        /// Updates the AST pane to display the given command's AST.
        /// </summary>
        /// <param name="ast">The AST root command.</param>
        private void updateAST(CoreEx.Cmd ast) {
            ASTreeView.Nodes.Clear();
            ASTreeView.Nodes.Add(new CmdTreeNode(-1, "Program"));
            addASTNode(ASTreeView.TopNode as CmdTreeNode, ast);
            ASTreeView.ExpandAll();
        }
        private void UpdateCommandGraph(FSEvent.CommandGraphUpdatedEventArgs args) {
            int id = cmdId(args.Command);
            var cg = args.Graph;
            ClearLastHighlightedCommand();
            var cmdNode = GetTreeNodeById(ASTreeView.TopNode, id) as CmdTreeNode;
            if (cmdNode != null) {
                HighlightCommand(cmdNode);
                cmdNode.GraphText = ContextGraph.prtEdges(cg);
                cmdNode.Graph = ContextGraph.clone(cg);
                ShowCommandGraph(cmdNode, false);
            }
        }

        // Menu and files management
        /// <summary>
        /// Update window title based on current file name (if any) and dirty flag.
        /// </summary>
        private void UpdateTitle() {
            var s = "Untitled";
            if (!string.IsNullOrEmpty(m_currentFileName)) s = Path.GetFileName(m_currentFileName);
            if (CodeTextBox.Text != m_baseCodeValue) s += "*";
            Text = s + " - " + m_baseTitle;
        }

        private struct AnalysisThreadParams {
            public readonly Core.Cmd cmd;
            public AnalysisThreadParams(Core.Cmd c) {
                cmd = c;
            }
        }

        private void optionsToolStripMenuItem_Click(object sender, EventArgs e) {
            var prompt = new Form {Width = 200, Height = 200, Text = "Settings"};

            var resultsLabel = new Label { Left = 10, Top = 13, Text = "Results Font Size" };
            var resultsInputBox = new NumericUpDown { Left = 113, Top = 10, Width = 60, Minimum = 6, Maximum = 72, Value = Properties.Settings.Default.resultFontSize };
            
            var codeLabel = new Label { Left = 10, Top = 43, Text = "Code Font Size" };
            var codeInputBox = new NumericUpDown { Left = 113, Top = 40, Width = 60, Minimum = 6, Maximum = 72, Value = Properties.Settings.Default.codeFontSize };

            var analysisLabel = new Label { Left = 10, Top = 73, Text = "Analysis log Font Size" };
            var analysisInputBox = new NumericUpDown { Left = 113, Top = 70, Width = 60, Minimum = 6, Maximum = 72, Value = Properties.Settings.Default.analysisFontSize };

            prompt.Controls.Add(resultsLabel);
            prompt.Controls.Add(resultsInputBox);
            prompt.Controls.Add(codeLabel);
            prompt.Controls.Add(codeInputBox);
            prompt.Controls.Add(analysisLabel);
            prompt.Controls.Add(analysisInputBox);

            resultsInputBox.ValueChanged += (o, args) => {
                Properties.Settings.Default.resultFontSize = resultsInputBox.Value;
                Properties.Settings.Default.Save();
                textBoxAnalysisResult.Font =
                    new Font(textBoxAnalysisResult.Font.FontFamily,
                             (float)resultsInputBox.Value);
            };

            codeInputBox.ValueChanged += (o, args) => {
                Properties.Settings.Default.codeFontSize = codeInputBox.Value;
                Properties.Settings.Default.Save();
                CodeTextBox.Font = new Font(CodeTextBox.Font.FontFamily,
                                            (float)codeInputBox.Value);
            };

            analysisInputBox.ValueChanged += (o, args) => {
                Properties.Settings.Default.analysisFontSize = analysisInputBox.Value;
                Properties.Settings.Default.Save();
                if (m_debugLogForm != null) 
                    m_debugLogForm.Font = new Font(m_debugLogForm.Font.FontFamily,
                                            (float)analysisInputBox.Value);
            };

            prompt.ShowDialog();
        }
    }
}